iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Vibe Coding

《我與 AI 的奇幻漂流:30 天,把「能跑」變成「能上線」》系列 第 26 篇

【Day 26|鯊魚】資安與 AI 安全審查:功能正常,為什麼可能會不安全?

  • 分享至 

  • xImage
  •  

昨天,CI 全綠了:lint、型別、單元測試、E2E,都通過了。

至少照著我預期的方式使用,網站現在是正常的。搜尋找得到專輯、網址打得開、權限也照規則運作。

但網站上線之後,來的人不會只照著我設計好的路走。有人會改網址、改參數、直接送 API 請求,甚至故意去試那些畫面上根本沒有提供的操作。這時候,「功能正常」就不夠了。還要知道:當有人不照規則使用時,我原本設下的限制還擋不擋得住?

這就是今天要看的資安問題。今天的文章將介紹一些開發網站時,可能會不小心忽視的安全問題,以及如何透過 AI 來輔助我們找出問題並修改。


一、CI 測的是正常使用,資安想的是不正常的使用

用這個系列曾用過的餐廳來比喻:

  • CI 像是假扮客人,照著菜單點餐、上菜、結帳,確認每一步都正常。
  • 資安 要想的是:有沒有人從後門走進廚房?有沒有人改了帳單上的金額?有沒有人冒用別的客人的名字點餐?
功能測試問的 資安問的
該看的資料,看得到嗎? 不該看的資料,看得到嗎?
按鈕按下去,能不能正常送出? 別人能不能替你按下去?

測試驗證的通常是「預期行為」;資安思考的是「非預期行為」。

資安測試也可以放進 CI,例如昨天測試的其中一條「訪客讀不到錯誤回報」的權限測試就是其中一個例子;差別在於思考的方向。


二、常見的漏洞

攻擊網站的人,大多不是衝著你來的。他們會用程式自動掃過大量網站,哪裡有洞就往哪裡咬,就像鯊魚聞到血腥味。下面是幾種最常見的,最後一欄是這次針對專案盤點的結果。

✅ 只代表「這次盤點沒找到可利用的問題」,不是保證安全;⚠️ 代表找到了,需要修或做出決定。

攻擊 一句話 比喻 專案目前的情況
越權存取 改一下網址或參數,就看到別人的資料 飯店房卡改個號碼,就打開別人的房間 ✅ 每張表都有 RLS;收藏只能讀寫自己的,後台在伺服器和資料庫各檢查一次管理員身分
XSS(跨站腳本) 把程式碼藏在內容裡,讓別人的瀏覽器執行 意見箱裡投的不是字條,是一張會動手的紙條 ✅ React 預設把內容當文字顯示;專案裡沒有直接插入 HTML 的寫法,外部連結也限制只能是 https://
CSRF(跨站請求偽造) 借用你已登入的身分,偷偷送出請求 有人拿你簽好名的空白單據去填 ✅ Server Actions 會比對請求來源;唯一自己寫的 API 也有檢查來源
SQL injection 輸入的內容被當成資料庫指令執行 在表單姓名欄寫「順便把全部資料給我」 ✅ 程式透過 Supabase 傳值,沒有自己拼 SQL;資料庫函式裡也沒有把輸入串進 SQL 指令
濫用 不停重試,或大量送出 一直試密碼鎖,或塞爆意見箱 ⚠️ 錯誤回報有頻率限制,但同時送出大量請求時可以超過上限(見 5.2)
開放重導向 讓你的網站,幫忙把人送到釣魚網站 用你家的門牌當指路牌,指向別人的陷阱 ⚠️ 登入後的跳轉可以被繞過(見 5.1)

這張表有兩件事值得注意:

  • 在 Biashelf 這種透過 Supabase 存取資料的網站裡,越權存取反而是更需要自己把關的風險。 不自己拼 SQL,SQL injection 的風險就低很多;但「誰可以看什麼」,要自己一條一條寫。登入只代表「我知道你是誰」,授權才決定「你可以做什麼」。
  • 框架的安全預設很重要,但不是無敵。 React 在顯示一般文字時會自動跳脫,擋掉大部分的 XSS;但只要把不可信的內容當成 HTML 或連結網址來用,風險就回來了。

其他常見的漏洞,這篇先不展開討論:

  • 登入狀態被別人拿去用:手機遺失、在公用電腦忘了登出。登入要維持多久,是方便和安全的取捨:銀行幾分鐘沒動就登出,社群網站可以一直登入。Biashelf 一般使用者只有收藏,維持 Supabase 的預設;真正敏感的是管理後台。
  • 點擊劫持、錯誤訊息洩漏:把網站嵌進透明框騙人點擊;錯誤頁面露出資料表名稱或程式路徑。
  • 冒名寄信:Day 24 的 SPF、DKIM、DMARC 就是在擋這個。
  • CI 本身:GitHub Actions 的 pull_request_target 權限較高、可以讀 Secrets,如果又去執行外部 PR 的程式碼,攻擊者就可能碰到它們。Day 25 用的是 pull_request。

三、讓 AI 當資安檢查員

3.1 全專案盤點的指令

Claude Code 內建的 /security-review,預設只檢查目前分支改了什麼。我第一次執行時它只看了三行,回報「沒有發現漏洞」。所以第一次導入時,要明確要求掃描整個專案。例如,我使用下列的同一份指令,分別交給 Claude Code 和 Codex:

請對整個專案做一次資安盤點(不只是目前分支的變更)。
只回報,不要修改任何檔案,也不要讀取或輸出 .env 的內容。

重點檢查:越權存取(RLS、伺服器端的身分檢查)、XSS、CSRF、
SQL injection(字串組 SQL)、開放重導向(登入後跳轉)、
金鑰(NEXT_PUBLIC_ 是否只有可公開的值)、濫用(頻率限制)。

每個發現請列出:嚴重程度、信心程度、檔案與行號、攻擊情境、建議修法。
不確定的請標註「需人工確認」,不要為了填滿報告而列出低信心的項目。

3.2 資安 skill:給 AI 一份檢查方法

除了自己寫指令,也可以替 AI 安裝資安 skill。skill 不是掃描引擎,而是一份寫給 AI 看的說明書:要看哪些地方、怎麼判斷是不是真的漏洞、報告怎麼寫。真正讀程式碼的,還是 AI 本身。例如:security-best-practices、security-threat-model 、security-review 等等。

不過,裝別人的 skill 之前,要先讀過內容。 skill 是會被 AI 照著做的指令,安裝來路不明的 skill,等於讓陌生人指揮你的 AI;而且 skill 更新之後,內容也可能跟當初不一樣。這跟安裝 npm 套件是同一個問題:你沒寫的東西,一樣會進到你的專案裡。


四、同一份程式碼,兩份報告

項目 Claude Code Codex 結果
開放重導向 ⚠️ 找到繞過方法,並實際執行驗證 ✅ 認為安全 真的漏洞
回報的頻率限制 ⚠️ 同時送出可以超過上限(低) ⚠️ 同一個問題(中) 兩邊都找到,嚴重程度判斷不同
偽造來源 IP 補充:Vercel 會覆寫,用戶端無法偽造 ⚠️ 中,需人工確認 在 Vercel 上不成立,Vercel 文件寫明會覆寫這個標頭,防止偽造

資安檢查有兩種常見的錯誤:漏報,真的有洞卻沒看到;誤報,看起來像洞,其實利用不了。這次兩種都遇到了:

  1. 漏報:同一份程式碼,兩個 AI 的結論可以不一樣。 Codex 對開放重導向的判斷是「未發現」,但那裡確實有洞。只問一個 AI,「沒找到」不等於「沒有」,所以有時候可以使用不同的 AI 交叉比對
  2. 有實際驗證的發現,比「我看過了」可信。 Claude Code 不只讀程式碼,還把有問題的函式實際跑了一次,直接給出證據。
  3. 誤報:AI 標註「需人工確認」的地方,就真的要人去查。 這次查了 Vercel 的文件,就排除了一個誤報。

五、修掉找到的洞

5.1 開放重導向

登入頁可以帶一個 next 參數,記住使用者原本在哪一頁,登入後跳回去。為了不被拿來跳到外部網站,程式會先擋掉 // 開頭的網址(瀏覽器會把 //evil.com 當成 https://evil.com),再把網址整理一遍。

問題就在順序。整理時,/./ 這種「目前資料夾」的寫法會被拿掉,所以 /.//evil.com 檢查時不是 // 開頭,順利通過,整理完卻變成 //evil.com。/..//evil.com、/%2e//evil.com 也一樣。

https://ithelp.ithome.com.tw/upload/images/20261010/20178017qWdAdzPn4b.png
【圖 1|開放重導向的攻擊流程】

修法是把順序反過來:先整理,再檢查,確認整理後的網址仍然在自己的網域。修好之後,把這三種寫法寫成測試放進 CI。

每個真的漏洞,修好之後,都盡量留下一條測試。

5.2 頻率限制被同時送出繞過

錯誤回報有限制:同一個來源,每小時最多 5 筆。程式的做法是:

先查:最近一小時送了幾筆? → 如果少於 5 筆 → 再寫入

一筆一筆送,這樣沒問題。但如果「同時」送出 50 筆,每一筆在查詢時都看到「目前 0 筆」,於是全部寫入成功,管理員的 Telegram 也會收到 50 則通知。

這種「先查再寫,中間被插隊」的問題,叫做競態條件(race condition)。就像兩個人同時看到電影院剩最後一個位子,都按下購買,結果同一個位子賣了兩次。本的修法,是把限制交給資料庫保證,例如鎖、唯一限制,或設計好的交易(transaction),而不是只靠程式先查再寫。

這次的決定:這個問題只會造成垃圾回報和通知洗版,不會洩漏資料。所以先在門口用 Vercel 的規則擋住大量請求,程式裡的競態條件記錄下來,之後再修。

5.3 套件漏洞:數字不等於風險

昨天 npm 回報 3 個已知漏洞,今天重跑變成 8 個,程式碼卻一行都沒改。漏洞資料庫一直在更新,所以數字會自己變。

但這 8 個追下去,只有 4 條來源:Next.js、PostCSS、sharp 和 ESLint 工具鏈,而且觸發條件大多跟 Biashelf 對不上,有一條還只在開發時使用。數字不等於風險,要看攻擊者有沒有路徑碰得到。

npm 建議的 npm audit fix --force,會把 ESLint 設定降回舊版,跟目前的 Next.js 對不上:一鍵修復可能修錯方向。這次的處理方式,是開分支升級 Next.js,讓 CI 跑過再合併。有了 CI,升級套件就比較敢做。


六、網路層:門口的警衛

前面講的,都是廚房裡的規矩。網路層則是門口的警衛:擋得住擠爆門口的人群,卻看不出某位客人改了帳單。兩者不能互相取代,這就是分層防禦。Biashelf 目前的防線是這樣:

https://ithelp.ithome.com.tw/upload/images/20261010/20178017T3iV6ltNng.png
【圖 2|Biashelf 的分層防禦】

6.1 Vercel 防火牆

Biashelf 的流量直接送到 Vercel,所以實際站在門口的,是 Vercel 的防火牆:

功能 做什麼 Biashelf
自動 DDoS 防護 異常的大量流量自動擋,擋掉的不收費;被攻擊時還能開 Attack Challenge Mode,讓所有訪客先過驗證 所有方案都有
自訂規則 依路徑、IP、國家,擋下或要求驗證 需要時再加
頻率限制規則 某個路徑,每個 IP 在一段時間內最多幾次 這次實作

這次替錯誤回報加了一條頻率限制規則:

路徑:/api/data-reports,方法:POST
同一個 IP 每分鐘超過 ⚠️ 次 → 直接拒絕

程式裡「先查再寫」的漏洞還在,但同時送來的大量請求,大部分在門口就被擋掉,進不到程式裡。這是緩解,不是修好。另外,在按用量計費的平台上,大量流量更常造成的傷害是帳單:沒被認定為攻擊的流量(例如大量爬蟲)一樣會計費,記得設好用量上限的通知。

6.2 Cloudflare:灰雲下還能用的

Cloudflare 的 WAF、Bot Fight Mode,要流量經過 Cloudflare 才有作用。Day 23 設的是灰雲,流量直接到 Vercel,所以開了也擋不到東西。改成橘雲可以做,但 Vercel 官方一般不建議:多繞一層會變慢,而且部分用來辨識機器人的訊號可能失真或缺失,Vercel 防火牆的偵測效果會下降。

灰雲下,Cloudflare 還是有幾樣可以用:

  • Turnstile:免費的人機驗證,不需要橘雲,任何網站都能用。像錯誤回報這種匿名表單,很適合加上。但畫面上出現勾勾不算完成,驗證結果一定要送到伺服器再確認一次,否則只是看起來有防護。
  • 帳號安全:Cloudflare 帳號開啟兩步驟驗證,網域保持轉移鎖。網域一旦被搶走,前面的防護就全部失效。

可以自行取捨是否要讓流量通過 Cloudflare。


七、AI 的資安盲點

這次的漏洞不是 AI 刻意寫壞的,但 Vibe Coding 有一些很常見的模式:AI 的目標是「讓錯誤消失」,不一定是「讓它安全」。

AI 很容易做的事 後果
遇到權限錯誤或測試失敗,直接關掉 RLS、拿掉檢查 錯誤消失了,門也打開了
為了方便,直接相信前端傳來的 user_id 越權:改一個參數就能操作別人的資料
把 secret key 加上 NEXT_PUBLIC_ 金鑰被打包進前端,任何人都看得到
看到漏洞警告,直接 npm audit fix --force 套件被降版,網站可能直接壞掉

還有一個容易忽略的:AI 本身能做什麼,也有可能是攻擊面的一部分。 Day 17 那次,AI 自己執行了 supabase db push --yes,直接改了遠端資料庫。AI 能執行的指令越多,出錯時影響的範圍就越大。

另一種新的漏洞,攻擊的是幫你寫網站的 AI:提示注入(prompt injection),把指令藏在 AI 會讀到的網頁、issue 或文件裡,讓它做你沒叫它做的事。像 Codex、Claude Code 這類會讀外部內容、又能執行指令的工具,特別要注意。


驗收

測試 預期結果
用 ?next=/.//evil.com 登入 停留在 Biashelf,不會跳到外部網站
開放重導向的三種寫法,寫成測試放進 CI CI 通過
短時間內大量送出錯誤回報 超過 Vercel 規則的上限後被拒絕
升級 Next.js 後重跑 npm audit 高風險、而且碰得到的漏洞已修補;其他的有明確的處置理由

換個領域

產品 最怕的鯊魚
電商 改價格、讀到別人的訂單
SaaS 跨公司讀到別人的資料
教育平台 學生看到老師的後台
金流 同一筆付款請求被重送

技術名詞不同,但問的其實都是同一件事:誰可以做什麼?有沒有人能做到他不該做的事?


結語與明日預告

今天的新觀念:

觀念 一句話
越權存取 改一下參數,就能碰到別人的資料
開放重導向 你的網站,幫忙把人送去釣魚網站
競態條件 先查再寫,中間被插隊
漏報、誤報 有洞沒看到;看起來像洞,其實利用不了
分層防禦 程式和網路各擋一層,不靠單一道防線

Day 25 把「正常使用」變成每次都會檢查的規則;今天反過來問:如果有人故意不照正常方式使用呢?AI 可以當很好的資安檢查員,但它說「沒問題」的時候,要問清楚:看了哪裡?證據是什麼?攻擊條件真的成立嗎?必要時,換另一個 AI 再看一次。

網站能正常運作,也擋住了幾隻鯊魚。但如果使用者根本找不到這個網站,或者網站出了問題,我自己卻不知道呢?明天,我們來讓網站被看見,也讓自己看得見網站的流量。

我們明天見。


上一篇
【Day 25|每日作息】CI/CD 與自動化測試:怎麼知道我沒有改壞東西
下一篇
【Day 27|信號彈】SEO、GA4 與 Sentry:讓網站被看見,也看得見自己
系列文
《我與 AI 的奇幻漂流:30 天,把「能跑」變成「能上線」》 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言